package main

import (
	"fmt"
	"net"
	"strings"
	"time"
)

type User struct {
	//名字
	name string
	//唯一id
	id string
	//管道
	msg chan string
}

// 创建一个全景的map结构，用于保存所有的用户
var allUsers = make(map[string]User)

// 定义一个message全局通道，用于接收任何人发送过来的消息
var message = make(chan string, 10)

// 将所有代码写在一个文件中，不做代码整理
func main() {
	//创建服务器
	listener, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println("net.Listen err:", err)
		return
	}
	//启动全局唯一的go程，负责监听message通道，写给所有的用户
	go broadcase()
	fmt.Println("服务器启动成功，监听中.....")
	for {
		fmt.Println("主go程监听中.....")
		//监听
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener.Accept err:", err)
			return
		}
		//建立连接
		fmt.Println("建立连接成功！")
		//启动处理业务的go程
		go handle(conn)
	}
}
func handle(conn net.Conn) {

	fmt.Println("启动业务.....")

	//客户端与服务器建立连接的时候，会有ip和port==>当成user的id
	clientAddr := conn.RemoteAddr().String()
	//创建User
	newUser := User{
		name: clientAddr,            //可以修改，会提供rename命令进行修改
		id:   clientAddr,            //id,不会修改，作为在map中的key
		msg:  make(chan string, 10), //注意要分配make空间
	}
	//添加USer到map
	allUsers[newUser.id] = newUser

	// 定义一个退出信号，用于监听client退出
	var isQuit = make(chan bool)
	//定义一个重置计数器的管道，用于告知watch函数，当前用户正在输入
	var resetTimer = make(chan bool)

	//启动一个go程，负责监听退出信号
	go watch(&newUser, conn, isQuit, resetTimer)
	//启动go程，负责将msg信息返回给客户端
	go writeBackToClient(&newUser, conn)

	//向message写入数据，当前用户上线的消息，用于通知所有人（广播）
	loginInfo := fmt.Sprintf("[%s]:[%s]====>上线了login!!", newUser.id, newUser.name)
	message <- loginInfo
	for {
		//具体业务逻辑
		buf := make([]byte, 1024)
		//读取客户端发送过来的数据
		cnt, err := conn.Read(buf)
		if cnt == 0 || err != nil {
			fmt.Println("客户端主动关闭ctrl + c,准备退出！")
			//map删除，用户，conn close掉
			//服务器还可以主动退出
			//在这里不进行真正的退出动作，而是发送一个退出信号，统一做退出处理，可以使用行动管道来做信号传递
			isQuit <- true
		}
		if err != nil {
			fmt.Println("conn.Read err:", err)
			return
		}
		fmt.Println("服务器接收客户端发送过来的数据为:", string(buf[:cnt-1]), ",cnt:", cnt)

		//--------------------业务逻辑处理   开始--------------
		//1.查询当前所有用户
		//  a.先判读结束到的数据是不是who ====>长度&&字符串
		userInput := string(buf[:cnt-1]) //用户输入的数据，最光辉一个是回车，-1去掉
		if len(userInput) == 3 && userInput == "\\who" {
			//b.遍历allUsers这个map(key:userid value:user本身),将id和name拼接成一个字符串，返回给客户端
			fmt.Println("用户查询即将所有的用户信息！")
			//这个切片包含所有的用户信息
			var userInfos []string
			//
			for _, user := range allUsers {
				userInfo := fmt.Sprintf("userId:%s,username:%s", user.id, user.name)
				userInfos = append(userInfos, userInfo)
			}
			//最终写回管道，一定是一个字符串
			r := strings.Join(userInfos, "\n")
			//将数据返回给当前用户
			newUser.msg <- r
		} else if len(userInput) > 9 && userInput[:6] == "\\rename" {
			//规则： rename|Duke
			//1.读取数据判读长度为7，判读字符是rename
			//2.使用|进行分割，获取|后面的部分作为名字
			//arry := strings.Split(userInput, "|")
			//name := arry[0]
			//3.更新用户名字newUser.name=Duke
			newUser.name = strings.Split(userInput, "|")[1]
			allUsers[newUser.id] = newUser //更新map中的user
			//4.通知客户端，更新成功
			newUser.msg <- "rename successfully!"
		} else {
			//如果用户输入的不是命令，只是普通聊天信息，那么只需要写的广播通道中，由其他的go程镜像常规转发
			message <- userInput
		}

		resetTimer <- true
		//--------------------业务逻辑处理   结束--------------
	}
}

// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcase() {
	fmt.Println("广播go程启动成功......")
	defer fmt.Println("broadcase 程序退出！")
	for {
		//1.从message中读取到数据
		fmt.Println("broadcast监听message中...")
		info := <-message

		fmt.Println("message接收到的消息：", info)
		//2.将数据写入到每一个用户的msg管道中
		for _, user := range allUsers {
			//如果msg是非缓冲的，那么会阻塞
			user.msg <- info
		}
	}
}

// 每个用户应该还有一个用来监听自己的msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User, conn net.Conn) {
	fmt.Printf("user:%s的go程正在监听自己的msg管道....\n", user.name)
	for data := range user.msg {
		fmt.Printf("user: %s 写回给客户端的数据为:%s\n", user.name, data)
		_, _ = conn.Write([]byte(data))

	}
}

// 启动一个go程,负责监听退出型号，触发后，进行清零工作：delete map，close conn都在这里
func watch(user *User, conn net.Conn, isQuit <-chan bool, resetTimer <-chan bool) {
	fmt.Println("启动监听退出信号的go程")
	defer fmt.Println("watch go程退出")
	for {
		select {
		case <-isQuit:
			logoutInfo := fmt.Sprintf("%s exit already!\n", user.name)
			fmt.Println("删除当前用户：", user.name)
			delete(allUsers, user.id)
			message <- logoutInfo
			conn.Close()
			return
		case <-time.After(60 * time.Second):
			logoutInfo := fmt.Sprintf("%s timeout exit already!\n", user.name)
			fmt.Println("删除当前用户：", user.name)
			delete(allUsers, user.id)
			message <- logoutInfo
			conn.Close()
			return
		case <-resetTimer:
			fmt.Printf("连接%s,重置计数器！\n", user.name)
		}
	}
}
